<?php

/**
 * This file is part of the Texy! formatter (http://texy.info/)
 *
 * Copyright (c) 2004-2007 David Grudl aka -dgx- <dave@dgx.cz>
 *
 * @version  $Revision: 119 $ $Date: 2007-04-13 19:04:57 +0000 (Fri, 13 Apr 2007) $
 * @package  Texy
 */

// security - include texy.php, not this file
if (!defined('TEXY')) die();



/**
 * Heading module
 */
class TexyHeadingModule extends TexyModule
{
    const
        DYNAMIC = 1,  // auto-leveling
        FIXED =   2;  // fixed-leveling

    protected $default = array('heading/surrounded' => TRUE, 'heading/underlined' => TRUE);

    /** @var string  textual content of first heading */
    public $title;

    /** @var array  generated Table of Contents */
    public $TOC;

    /** @var bool  autogenerate ID */
    public $generateID = FALSE;

    /** @var string  prefix for autogenerated ID */
    public $idPrefix = 'toc-';

    /** @var int  level of top heading, 1..6 */
    public $top = 1;

    /** @var int  balancing mode */
    public $balancing = TexyHeadingModule::DYNAMIC;

    /** @var array  when $balancing = TexyHeadingModule::FIXED */
    public $levels = array(
        '#' => 0,  //  #  -->  $levels['#'] + $top = 0 + 1 = 1  --> <h1> ... </h1>
        '*' => 1,
        '=' => 2,
        '-' => 3,
    );

    /** @var array  used ID's */
    private $usedID;

    /** @var array */
    private $dynamicMap;

    /** @var int */
    private $dynamicTop;



    public function begin()
    {
        $this->texy->registerBlockPattern(
            array($this, 'patternUnderline'),
            '#^(\S.*)'.TEXY_MODIFIER_H.'?\n'
          . '(\#{3,}|\*{3,}|={3,}|-{3,})$#mU',
            'heading/underlined'
        );

        $this->texy->registerBlockPattern(
            array($this, 'patternSurround'),
            '#^(\#{2,}+|={2,}+)(.+)'.TEXY_MODIFIER_H.'?()$#mU',
            'heading/surrounded'
        );

        $this->title = NULL;
        $this->usedID = array();

        // clear references
        $foo1 = array(); $this->TOC = & $foo1;
        $foo2 = array(); $this->dynamicMap = & $foo2;
        $foo3 = -100; $this->dynamicTop = & $foo3;
    }



    /**
     * Callback for underlined heading
     *
     *  Heading .(title)[class]{style}>
     *  -------------------------------
     *
     * @param TexyBlockParser
     * @param array      regexp matches
     * @param string     pattern name
     * @return TexyHtml|string|FALSE
     */
    public function patternUnderline($parser, $matches)
    {
        list(, $mContent, $mMod, $mLine) = $matches;
        //  $matches:
        //    [1] => ...
        //    [2] => .(title)[class]{style}<>
        //    [3] => ...

        $mod = new TexyModifier($mMod);
        $level = $this->levels[$mLine[0]];

        // event wrapper
        if (is_callable(array($this->texy->handler, 'heading'))) {
            $res = $this->texy->handler->heading($parser, $level, $mContent, $mod, FALSE);
            if ($res !== Texy::PROCEED) return $res;
        }

        return $this->solve($level, $mContent, $mod, FALSE);
    }



    /**
     * Callback for surrounded heading
     *
     *   ### Heading .(title)[class]{style}>
     *
     * @param TexyBlockParser
     * @param array      regexp matches
     * @param string     pattern name
     * @return TexyHtml|string|FALSE
     */
    public function patternSurround($parser, $matches)
    {
        list(, $mLine, $mContent, $mMod) = $matches;
        //    [1] => ###
        //    [2] => ...
        //    [3] => .(title)[class]{style}<>

        $mod = new TexyModifier($mMod);
        $level = 7 - min(7, max(2, strlen($mLine)));
        $mContent = rtrim($mContent, $mLine[0] . ' ');

        // event wrapper
        if (is_callable(array($this->texy->handler, 'heading'))) {
            $res = $this->texy->handler->heading($parser, $level, $mContent, $mod, TRUE);
            if ($res !== Texy::PROCEED) return $res;
        }

        return $this->solve($level, $mContent, $mod, TRUE);
    }



    /**
     * Finish invocation
     *
     * @param int
     * @param string
     * @param TexyModifier
     * @param bool
     * @return TexyHtml
     */
    public function solve($level, $content, $mod, $isSurrounded)
    {
        $tx = $this->texy;
        $el = new TexyHeadingElement;
        $mod->decorate($tx, $el);

        $el->userData['level'] = $level;
        $el->userData['top'] = $this->top;
        $el->userData['map'] = NULL;

        if ($this->balancing === self::DYNAMIC) {
            if ($isSurrounded) {
                $this->dynamicTop = max($this->dynamicTop, $this->top - $level);
                $el->userData['top'] = & $this->dynamicTop;
            } else {
                $this->dynamicMap[$level] = $level;
                $el->userData['map'] = & $this->dynamicMap;
            }
        }
        $el->parseLine($tx, trim($content));

        // document title
        $title = $tx->_toText($el->getContent());
        if ($this->title === NULL) $this->title = $title;

        // Table of Contents
        if ($this->generateID && empty($el->id)) {
            $id = $this->idPrefix . Texy::webalize($title);
            $counter = '';
            if (isset($this->usedID[$id . $counter])) {
                $counter = 2;
                while (isset($this->usedID[$id . '-' . $counter])) $counter++;
                $id .= '-' . $counter;
            }
            $this->usedID[$id] = TRUE;
            $el->id = $id;
        }

        $TOC = array(
            'id' => isset($el->id) ? $el->id : NULL,
            'title' => $title,
            'level' => 0,
        );
        $this->TOC[] = & $TOC;
        $el->userData['TOC'] = & $TOC;

        return $el;
    }


} // TexyHeadingModule







/**
 * HTML ELEMENT H1-6
 */
class TexyHeadingElement extends TexyHtml
{
    public $elName = 'h?';


    public function startTag()
    {
        $level = $this->userData['level'];

        if ($this->userData['map']) {
            asort($this->userData['map']);
            $level = array_search($level, array_values($this->userData['map']), TRUE);
        }

        $level += $this->userData['top'];

        $this->elName = 'h' . min(6, max(1, $level));
        $this->userData['TOC']['level'] = $level;
        return parent::startTag();
    }

} // TexyHeadingElement
